/**
 * 
 */
package info.sswap.ontologies.modularity;

import info.sswap.ontologies.modularity.owlapi.LocalOWLAPIModuleExtractor;
import info.sswap.ontologies.modularity.owlapi.OWLAPIModule;
import info.sswap.ontologies.modularity.repo.OntologyRepository;
import info.sswap.ontologies.modularity.repo.OntologyRepositoryException;
import info.sswap.ontologies.modularity.repo.RepositoryEntry;
import info.sswap.ontologies.util.URIUtils;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import uk.ac.manchester.cs.owlapi.modularity.ModuleType;

/**
 * Extract multiple modules for terms with special URI annotations, e.g., rdfs:isDefinedBy
 * 
 * @author Pavel Klinov
 *
 */
public class SSWAPModuleExtractor implements ExtendedModuleExtractor {

	private final OntologyRepository mOntoRepo;
	private List<ModuleExtraction> mMESequence = new ArrayList<ModuleExtraction>();
	
	public SSWAPModuleExtractor(OntologyRepository repo) {
		
		if (repo == null) {
			throw new IllegalArgumentException("Ontology repository must be set");
		}
		else {
			mOntoRepo = repo;
		}
	}

	public List<ModuleExtraction> getExtractionList() {
		return mMESequence;
	}
	
	/**
	 * Returns the ontology descriptors for the given term 
	 * based on rdfs:isDefinedBy annotations and the term index and what we have in the repository
	 */
	private Collection<OntologyDescriptor> getOntologyDescriptors(URI term, Module module) {
		Collection<RepositoryEntry> repEntries = null;
		URI namespace = URI.create(URIUtils.getNamespace(term));
		//First, see if there's a specific annotation
		URI ontologyURI = module.getDefiningOntologyURI(term);
		//Second, see if the term's namespace is annotated
		if (ontologyURI == null) {
			ontologyURI = module.getDefiningOntologyURI(namespace);
		}
		
		try {
			if (ontologyURI == null) {
				//ask the repository's term index
				repEntries = mOntoRepo.getEntriesForTerm(term);
				//if there's still nothing, try asking the repository for the namespace
				repEntries = repEntries.isEmpty() ? mOntoRepo.getEntriesForTerm(namespace) : repEntries; 
			}
			else {
				//ask the repository about a specific ontology
				RepositoryEntry entry = mOntoRepo.getEntryForOntology(ontologyURI);
				
				repEntries = entry == null ? Collections.<RepositoryEntry>emptyList() : Collections.singleton(entry);
			}
		} catch (OntologyRepositoryException e) {
			//TODO Log it, something couldn't be loaded from the repository
			repEntries = Collections.<RepositoryEntry>emptyList();
		}
		
		URI physURI = null;
		Collection<OntologyDescriptor> descriptors = new ArrayList<OntologyDescriptor>(repEntries.size());
		
		if (!repEntries.isEmpty()) {
			for (RepositoryEntry entry : repEntries) {
				descriptors.add(new OntologyDescriptor(entry));
			}
		}
		//See if it is a SSWAP meet term. If yes, set the physical URI for dereferencing
		else if ((physURI = getDereferenceableURI(term)) != null) {
			descriptors.add(new OntologyDescriptor(term, physURI));
		}
		
		return descriptors;
	}

	protected URI getDereferenceableURI(URI term) {
		String uriStr = term.toString();
		
		if (uriStr.startsWith(Constants.SSWAPMEET_PREFIX)) {
			return term;
		}
		else if (uriStr.startsWith("http://localhost")) {
			return term;
		}
		else {
			return null;
		}
	}

	@Override
	public Module extract(Collection<TermOntologyPair> terms, ModuleType moduleType, boolean expandInitialSignature) throws ModuleExtractionException {
		Map<OntologyDescriptor, Set<URI>> ontoTermsMap = initOntoTermMap(terms);
		Module module = createEmptyModule();
		Set<URI> totalSig = new HashSet<URI>();
		//handle the signature expansion here
		if (expandInitialSignature) {
			for (Map.Entry<OntologyDescriptor, Set<URI>> entry : ontoTermsMap.entrySet()) {
				if (entry.getKey().getRepositoryEntry() != null) {
					expandInitialSignature(entry.getValue(), entry.getKey().getRepositoryEntry());
				}
			}
		}
		
		mMESequence.clear();
		//The main extraction loop
		do {				
			Map<OntologyDescriptor, Set<URI>> newOntoTermsMap = new HashMap<OntologyDescriptor, Set<URI>>();
			//Now extract modules
			for (OntologyDescriptor ontoDescr : ontoTermsMap.keySet()) {
				Set<URI> ontoSig = ontoTermsMap.get(ontoDescr);
				Module ontoModule = null;
				ModuleExtraction meRequest = new ModuleExtraction(ontoDescr.getLogicalURI(), ontoSig);
				//Now get the right module extractor
				ModuleExtractor extractor = null;
				RepositoryEntry repoEntry = ontoDescr.getRepositoryEntry();
				
				try {
					if (repoEntry != null) {
						extractor = repoEntry.getModuleExtractor(moduleType);
					}
					else if (ontoDescr.isDereferenceable()) {
						//Only for sswapmeet and local terms?
						extractor = new LocalOWLAPIModuleExtractor(ontoDescr.getPhysicalURI(), moduleType);
					}
					else continue;
				
					totalSig.addAll(ontoSig);
					ontoModule = extractor.extract(ontoSig);
					meRequest.setModule(ontoModule);
					
				} catch (ModuleExtractionException e) {
					//TODO Log it properly
					e.printStackTrace();
					//Most probably failed to load the ontology for some term, so skip it
					ontoModule = createEmptyModule();
				} catch (OntologyRepositoryException e) {
					throw new ModuleExtractionException(e); 
				}
				finally {
					if (repoEntry != null) repoEntry.close();
				}
				
				mMESequence.add(meRequest);
				//See if we got some new terms
				for (URI term : ontoModule.getSignature()) {
					//See if the term is defined externally
					if (totalSig.contains(term)) continue;
					
					for (OntologyDescriptor termOntoDescr : getOntologyDescriptors(	term, ontoModule)) {
						if (!ontoDescr.getLogicalURI().equals(termOntoDescr.getLogicalURI()) && processableOnline(termOntoDescr)) {
							// The term is defined in some another ontology
							Set<URI> ontoTerms = newOntoTermsMap.get(ontoDescr);

							ontoTerms = ontoTerms == null ? new HashSet<URI>() : ontoTerms;
							ontoTerms.add(term);
							newOntoTermsMap.put(termOntoDescr, ontoTerms);
						}
					}
					
					totalSig.add(term);
				}	
				//Update the main module and the signature
				module = module.union(ontoModule);
			}
			
			ontoTermsMap.clear();
			ontoTermsMap.putAll(newOntoTermsMap);
		} while (!ontoTermsMap.isEmpty());
		
		return module;
	}

	private Module createEmptyModule() {
		return new OWLAPIModule();
	}

	/*
	 * Expands the initial signature by asking the repository entry to
	 * also add closely related terms (i.e. those which appear in the same axioms)
	 */
	private void expandInitialSignature(Set<URI> sig, RepositoryEntry entry) {
		for (URI term : new ArrayList<URI>(sig)) {
			try {
				//Grab the signature of all axioms where the term appears
				Collection<URI> initialSignature = entry.getRelatedTerms(term);
				
				//System.out.println("Expanded signature for resolving " + term + ":");
				//System.out.println(initialSignature);
				
				sig.addAll(initialSignature);
			} catch (OntologyRepositoryException e) {
				// TODO Log it. The repository failed to do that, so just use the initial term
				e.printStackTrace();
			}
		}	
	}

	/*
	 * Here we decide if we can do module extraction from this ontology
	 */
	private boolean processableOnline(OntologyDescriptor termOntoDescr) {
		return termOntoDescr.getRepositoryEntry() != null || termOntoDescr.isDereferenceable();
	}

	private Map<OntologyDescriptor, Set<URI>> initOntoTermMap(Collection<TermOntologyPair> termPairs) {
		Map<OntologyDescriptor, Set<URI>> ontoTermMap = new HashMap<OntologyDescriptor, Set<URI>>();
		
		for (TermOntologyPair pair : termPairs) {
			OntologyDescriptor setDescr = pair.getOntologyDescriptor();
			Collection<RepositoryEntry> repEntries = null;
			
			try {
				if (setDescr != null) {
					//the ontology descriptor is set
					RepositoryEntry entry = mOntoRepo.getEntryForOntology(setDescr.getLogicalURI());
					
					repEntries = entry == null ? Collections.<RepositoryEntry>emptyList() :Collections.singletonList(entry);
				}
				else {
					//no ontology descriptor, see what we have in the repository for this term
					repEntries = mOntoRepo.getEntriesForTerm(pair.getTermURI());
					repEntries = !repEntries.isEmpty() ? repEntries : mOntoRepo.getEntriesForTerm(URI.create(URIUtils.getNamespace(pair.getTermURI())));
					
				}
			} catch (OntologyRepositoryException e) {
				// TODO Log the exception, something can't be loaded
				e.printStackTrace();
			}
			
			if (repEntries.isEmpty()) {
				// Use the term's URI as physical URI if it's processable online
				URI physURI = null;
				
				if ((physURI = getDereferenceableURI(pair.getTermURI())) != null) {
					ontoTermMap.put(new OntologyDescriptor(pair.getTermURI(), physURI), Collections.singleton(pair.getTermURI()));
				}
				else {
					System.err.println(pair.getTermURI() + " is not just-in-time dereferenceable");
				}
			} else {

				for (RepositoryEntry entry : repEntries) {
					OntologyDescriptor descr = new OntologyDescriptor(entry);
					Set<URI> sig = ontoTermMap.get(descr);

					sig = sig == null ? new HashSet<URI>() : sig;
					sig.add(pair.getTermURI());
					ontoTermMap.put(descr, sig);
				}
			}
		}
		
		return ontoTermMap;
	}
}